/* main.c, Ait, BSD 3-Clause, Kevin Bloom, 2023-2025,
   Derived from: Atto January 2017
   Derived from: Anthony's Editor January 93
*/

#include <sys/types.h>
#include "header.h"
#include "termbox.h"
#include "util.h"

int done;
kill_t scrap = { NULL, 0 };
kill_t kill_ring[KILLRING_SIZE];
kill_t shell_ring[SHELLRING_SIZE];

char_t input[K_BUFFER_LENGTH];
char_t *tinput;
int msgflag;
char msgline[TEMPBUF];
char temp[TEMPBUF];
char *gtemp = NULL;
long ngtemp = 0;
char searchtext[STRBUF_L];
char replace[STRBUF_L];
int found_point = -1;
int search_dir = 1;
int universal_argument = 0;
int numeric_argument = 0;
int negated = FALSE;
int submatch = 0;
uint32_t input_char = 0;
int undoset_flag = FALSE;
char editor_dir[PATH_MAX+1];
int record_input = FALSE;
int record_buffer_index = 0;
int run_buffer_index = 0;
struct tb_event record_buffer[256];
int execute_kbd_macro = FALSE;
int undo_index = -1;
char unicode_buf[7];
char character[1];
int lastcommand = KBD_DEFAULT;
int currentcommand = KBD_DEFAULT;
int window_mode = WINDOW_DEFAULT;
int ignorenotbound = FALSE;
char *backup_dir = NULL;
char *switch_command = NULL;
int lastcol = 0;
int lastline = 0;
char lastchar = 0;
char lastsymb = 0;
point_t recentbracket = -1;
const char* term;
dyna_t dynaex;
int io = 0;

keymap_t *key_return;
keymap_t *key_map;
buffer_t *curbp;		/* current buffer */
buffer_t *bheadp;		/* head of list of buffers */
window_t *curwp;
window_t *wheadp;
buffer_t *lastbp = NULL;	/* last active buffer */
int LINES;
int COLS;
int MSGLINE;			/*  */

static void graceful_exit()
{
  tb_shutdown();
  exit(1);
}

static void cont()
{
  int ol = LINES, oc = COLS;
  tb_init();
  LINES = tb_height();
  COLS = tb_width();
  MSGLINE = LINES-1;
  tb_set_input_mode(TB_INPUT_ALT);
  tb_clear();
  if(ol != LINES || oc != COLS)
    resize();
  update_display();
  redraw();
}

static void setup_signal_handlers()
{
  struct sigaction action;

  memset(&action, 0, sizeof(struct sigaction));

  action.sa_handler = graceful_exit;
  sigaction(SIGTERM, &action, NULL);
  sigaction(SIGINT, &action, NULL);
  sigaction(SIGQUIT, &action, NULL);
  sigaction(SIGHUP, &action, NULL);

  action.sa_handler = cont;
  sigaction(SIGCONT, &action, NULL);

  signal(SIGPIPE, SIG_IGN);
}

void check_flags(char **argv, int idx)
{
  if(!strcmp(argv[idx], "-v")) {
    fprintf(stdout, "%s\n\r", VERSION);
    exit(0);
  }
  if(!strcmp(argv[idx], "-h")) {
    fprintf(stdout, "%s\n\r\nUsage: ait [vh] [file]... +/-line...\n\r       ait [-b backup_dir] [file]... +/-line...\n\r\n-h             Print help and exit\n\r-v             Print version and exit\n\r-b backup_dir  Set the backup directory\n\r-s switch_cmd  Set the switch command\n\r\n", VERSION);
    exit(0);
  }
  return;
}

int main(int argc, char **argv)
{
  int ret, u, buffers = 0;
  int line = 0, current = 0, lastln = 0;
  int args = 0, v = 1;
  point_t p;

  if(2 <= argc) {
    for(int i = argc - 1; i >= 1; i--)
      check_flags(argv, i);
    if(!strcmp(argv[1], "-b") && argc >= 2) {
      if((backup_dir = strdup(argv[2])) == NULL) {
        fatal("%s: Failed to allocate required memory.\n");
      }
    }
    if(argc >= 2 && !strcmp(argv[1], "-s") && backup_dir == NULL) {
      if((switch_command = strdup(argv[2])) == NULL) {
        fatal("%s: Failed to allocate required memory.\n");
      }
    }
    if(argc >= 4 && !strcmp(argv[3], "-s")) {
      if((switch_command = strdup(argv[4])) == NULL) {
        fatal("%s: Failed to allocate required memory.\n");
      }
    }
  }

  ret = tb_init();
  LINES = tb_height();
  COLS = tb_width();
  MSGLINE = LINES-1;
  character[0] = '\0';
  term = getenv("LANG");
  dynaex.query = NULL;
  dynaex.nquery = -1;
  dynaex.nresult = -1;
  dynaex.start = -1;
  dynaex.end = -1;
  dynaex.sp = -1;

  if (ret) {
    fprintf(stderr, "Failed with error code %d\n", ret);
    exit(1);
  }

  getcwd(editor_dir, sizeof(editor_dir));
  strcat(editor_dir, "/");

  tb_set_input_mode(TB_INPUT_ALT);
//   tb_set_output_mode(TB_OUTPUT_216);
  tb_clear();

  setup_signal_handlers();

  // initialize the kill-ring
  for(int i = KILLRING_SIZE-1; i > 0; i--) {
    struct kill_t k;
    k.data = NULL;
    k.len = 0;
    kill_ring[i] = k;
  }

  /* TODO: this is really dumb... */
  if(backup_dir == NULL && switch_command == NULL) {
    args = 1 < argc;
    v = 1;
  } else if(backup_dir != NULL && switch_command != NULL) {
    args = 5 < argc;
    v = 5;
  } else if(backup_dir != NULL || switch_command != NULL) {
    args = 3 < argc;
    v = 3;
  }

  if (args) {
    for(; v < argc; v++) {
      buffers++;
      curbp = find_buffer(argv[v], TRUE, TRUE);
      (void) insert_file(argv[v], FALSE);
      /* Save filename regardless of load() success. */
      if(argv[v][0] == '-') {
        check_flags(argv, v);
      }
      curbp->b_path = TRUE;
      curbp->b_line = 1;
      curbp->b_pcol = 0;
      if (!growgap(curbp, CHUNK))
        fatal("%s: Failed to allocate required memory.\n");
      movegap(curbp, 0);
      if(argv[v+1]) {
        if(argv[v+1][0] == '-' || argv[v+1][0] == '+') {
          if(argv[v+1][0] == '+') {
            argv[v+1]++;
            line = atoi(argv[v+1]);
          } else {
            argv[v+1]++;
            get_line_stats(&current, &lastln, curbp);
            line = lastln - atoi(argv[v+1]);
            if(line < 0)
              line = 0;
          }
          p = line_to_point(line);
          if (p != -1) {
            curbp->b_point = p;
            curbp->b_opoint = p;
            curbp->b_cpoint = p;
            if (curbp->b_epage < pos(curbp, curbp->b_ebuf)) curbp->b_reframe = 1;
            curbp->b_line = line;
            msg("Line %d", line);
          } else {
            msg("Line %d, not found", line);
          }
          v++;
        }
      }
    }
  } else {
   curbp = find_buffer("*scratch*", TRUE, TRUE);
   strncpy(curbp->b_bname, "*scratch*", STRBUF_S);
   curbp->b_path = FALSE;
  }

  if(switch_command == NULL) {
    asprintf(&switch_command, "!dd of=/tmp/ait-switch-temp.txt ; clear ; cat /tmp/ait-switch-temp.txt | awk '{ print NR \". \" $0 }' ; read -p \"> \" AIT_ANSWER ; cat /tmp/ait-switch-temp.txt | awk -va=\"$AIT_ANSWER\" 'NR == a {print $0}' > /tmp/ait-temp.txt ; rm /tmp/ait-switch-temp.txt");
  }

  key_map = keymap;

  submatch = 0;
  wheadp = curwp = new_window();
  associate_b2w(curbp, curwp);
  one_window(curwp);
  if(buffers > 1) {
    chop_window();
    if(curbp->b_next == NULL)
      next_buffer();
  }
  while (!done) {
    update_display();
    if(curwp->w_recenter) {
      recenter();
      curwp->w_recenter = FALSE;
      update_display();
    }
    recentbracket = -1;
    curbp->b_opoint = curbp->b_point;
    curbp->b_opage = curbp->b_page;
    tinput = get_key(key_map, &key_return);
    strcpy((char *)input, (const char *)tinput);
    if (key_return != NULL) {
      /* TODO: a better way to figure out editing commands */
      if(key_return->key_desc[2] == 'd' || key_return->key_desc[4] == '%' ||
          key_return->key_desc[2] == 'i' || key_return->key_desc[2] == 'k' ||
          key_return->key_desc[2] == '/' || key_return->key_desc[2] == 'm' ||
          key_return->key_desc[2] == 't' || key_return->key_desc[2] == 'y' ||
          key_return->key_desc[4] == 'd' || key_return->key_desc[4] == 'i' ||
          key_return->key_desc[4] == 't' || key_return->key_desc[4] == '\\' ||
          key_return->key_desc[4] == 'k' || key_return->key_desc[4] == '/' ||
          key_return->key_desc[4] == 'l' || key_return->key_desc[4] == 'c' ||
          key_return->key_desc[4] == 'z' ||
          key_return->key_desc[4] == 'Z' || (key_return->key_desc[4] == 'b'
          && key_return->key_desc[5] == 'k') || key_return->key_desc[2] == 'h') {
         if(key_return->key_bytes[0] != TB_KEY_CTRL_W && key_return->key_bytes[0] != TB_KEY_PGDN)
           curbp->b_mark = NOMARK;
        if(is_file_modified(curbp->b_fname) && !file_was_modified_prompt()) {
          continue;
        }
      }
      submatch = 0;
      if(execute_kbd_macro) {
        (key_return->func)();
      } else {
        u = numeric_argument > 0 ? numeric_argument : power(4, universal_argument);
        if(numeric_argument > 0 &&
           key_return->universal_argument_action != UA_PREVENT)
        key_return->universal_argument_action = UA_REPEAT;
        switch(key_return->universal_argument_action) {
        case UA_REPEAT:
          if(u > 1)
            shift_pmark(TRUE, curbp->b_point);
          for(; u > 0; u--)
            (key_return->func)();
          universal_argument = 0;
          numeric_argument = 0;
          /* For gotochar */
          character[0] = '\0';
          if(curbp->b_point > curbp->b_epage || curbp->b_point < curbp->b_page)
            curbp->b_reframe = TRUE;
          break;
        default:
          (key_return->func)();
          break;
        }
      }
      if(temp[0] != 0)
        memset(temp, 0, TEMPBUF);
      if(key_return->key_desc[2] != 'u')
        universal_argument = 0;
    } else if(submatch > 0) {
      // do nothing
    } else {
      submatch = 0;
      if(unicode_buf[0] != '\0' || *input > 31 || *input == 13 || *input == 9) {
        curbp->b_mark = NOMARK;
        if(is_file_modified(curbp->b_fname) && !file_was_modified_prompt()) {
          continue;
        }
      }
      if(unicode_buf[0] != '\0') {
        if(!execute_kbd_macro)
          u = numeric_argument > 0 ? numeric_argument : power(4, universal_argument);
        else
          u = 1;
        char tempbuf[7];
        strncpy(tempbuf, unicode_buf, 7);
        for(; u > 0; u--) {
          unicode_buf[0] = tempbuf[0];
          insert_unicode();
        }
        if(is_bracket(input[0], FALSE, NULL)) {
           int dir = (input[0] == '}' || input[0] == ']' || input[0] == ')' || input[0] == '>');
           curbp->b_point--;
           recentbracket = find_matching_bracket(curbp, curwp, dir ? -1 : 1, TRUE);
           curbp->b_point++;
         }
      }
      else if(!ignorenotbound)
        msg("Not bound");
      else if(ignorenotbound) {
        msg("");
        ignorenotbound = FALSE;
      }
      if(!execute_kbd_macro) {
        universal_argument = 0;
        numeric_argument = 0;
      }
    }
    /* For gotochar */
    character[0] = '\0';

    if(currentcommand != KBD_DELETE_CHAR &&
       currentcommand != KBD_DELETE_WORD &&
       currentcommand != KBD_CUT &&
       currentcommand != KBD_EXPAND &&
       currentcommand != KBD_UNDO
       ) {
      if(curbp->b_opoint > curbp->b_point) {
        curbp->b_opoint--;
        while(curbp->b_opoint >= curbp->b_point) {
          if(*ptr(curbp, curbp->b_opoint) == '\n')
            curbp->b_line--;
          curbp->b_opoint--;
        }
      } else if(curbp->b_opoint < curbp->b_point) { /*  */
        while(curbp->b_opoint < curbp->b_point) {
          if(*ptr(curbp, curbp->b_opoint) == '\n')
            curbp->b_line++;
          curbp->b_opoint++;
        }
      }
    }
    if(currentcommand != lastcommand) {
      if(lastcommand == KBD_EXPAND) {
        if(dynaex.query != NULL) {
          free(dynaex.query);
          dynaex.query = NULL;
        }
        dynaex.nquery = -1;
        dynaex.nresult = -1;
        dynaex.start = -1;
        dynaex.end = -1;
        dynaex.sp = -1;
        dynars_t *dr;
        dr = dynaex.results;
        while(dynaex.results != NULL) {
          dr = dynaex.results;
          dynaex.results = dynaex.results->d_next;
          if(dr->result != NULL) {
            free(dr->result);
            dr->result = NULL;
          }
          if(dr != NULL) {
            free(dr);
            dr = NULL;
          }
        }
      }
      lastcommand = currentcommand;
    }
    currentcommand = KBD_DEFAULT;
  }

  if (scrap.data != NULL) free(scrap.data);
  tb_set_cursor(0, LINES-1);
  tb_present();
  tb_shutdown();
  return 0;
}

void fatal(char *msg)
{
  tb_present();
  fprintf(stderr, msg, PROG_NAME);
  exit(1);
}

void msg(char *msg, ...)
{
  va_list args;
  va_start(args, msg);
  (void)vsprintf(msgline, msg, args);
  va_end(args);
  msgflag = TRUE;
  if(strlen(msgline) > COLS) {
    msgline[COLS] = '\0';
  }
}
